/**
 *  Copyright 2007-2008 University Of Southern California
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */


package edu.isi.pegasus.planner.transfer;

import edu.isi.pegasus.planner.common.PegasusProperties;

import edu.isi.pegasus.common.logging.LogManager;
import edu.isi.pegasus.common.logging.LogManagerFactory;

import java.util.Iterator;
import java.util.TreeMap;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
import java.util.StringTokenizer;

/**
 * A common class, that builds up the state from the properties to determine
 * whether a user wants certain type of transfer jobs for particular site to
 * run remotely. This allows a user to override the default behavior of how
 * Pegasus decides whether a transfer job runs locally ( on the submit host) or
 * remotely.
 *
 *
 * @author Karan Vahi
 * @version $Revision: 2567 $
 */

public class RemoteTransfer {

    /**
     * The constant to apply to all sites.
     */
    public static final String ALL_SITES = "*";

    /**
     * The property name to get the sites for which all transfers need to
     * be executed remotely.
     */
    public static final String ALL_TRANSFERS_REMOTE_PROPERTY =
                                        "pegasus.transfer.*.remote.sites";


    /**
     * The property name to get the sites for which stage-in transfers need to
     * be executed remotely.
     */
    public static final String STAGE_IN_TRANSFERS_REMOTE_PROPERTY =
                                        "pegasus.transfer.stagein.remote.sites";

    /**
     * The property name to get the sites for which inter site transfers need
     * to be executed remotely.
     */
    public static final String INTER_TRANSFERS_REMOTE_PROPERTY =
                                        "pegasus.transfer.inter.remote.sites";

    /**
     * The property name to get the sites for which stage-out transfers need to
     * be executed remotely.
     */
    public static final String STAGE_OUT_TRANSFERS_REMOTE_PROPERTY =
                                        "pegasus.transfer.stageout.remote.sites";

    
    /**
     * An internal table that maps remote transfer type to the corresponding
     * property.
     */
    private static  Map mPropertyTable;

    /**
     * The handle to the properties object holding the properties relevant to
     * Pegasus.
     */
     private PegasusProperties mProps;

     /**
      * The handle to the logging object.
      */
     private LogManager mLogger;

     /**
      * The map indexed by site name, that contains the state for all the sites.
      */
     private Map mStateMap;

     /**
      * Singleton access to the type table
      * Contains the mapping of a property to the third party transfer type
      *
      * @return map
      */
    private static Map propertyTable(){
        //singleton access
        if (mPropertyTable == null) {
            mPropertyTable = new TreeMap();
            mPropertyTable.put(new Integer(TransferState.STAGE_IN_REMOTE_TYPE),
                               STAGE_IN_TRANSFERS_REMOTE_PROPERTY);
            mPropertyTable.put(new Integer(TransferState.INTER_REMOTE_TYPE),
                               INTER_TRANSFERS_REMOTE_PROPERTY);
            mPropertyTable.put(new Integer(TransferState.STAGE_OUT_REMOTE_TYPE),
                               STAGE_OUT_TRANSFERS_REMOTE_PROPERTY);
            mPropertyTable.put(new Integer(TransferState.ALL_REMOTE_TYPE),
                               ALL_TRANSFERS_REMOTE_PROPERTY);


        }
        return mPropertyTable;
    }


     /**
      * The default constructor.
      */
     public RemoteTransfer() {
         mProps  = PegasusProperties.getInstance();
         mLogger =  LogManagerFactory.loadSingletonInstance( );
         mStateMap = new TreeMap();
     }


     /**
      * The overloaded constructor.
      *
      * @param properties   handle to the properties required.
      */
     public RemoteTransfer(PegasusProperties properties) {
         mProps  = properties;
         mLogger =  LogManagerFactory.loadSingletonInstance( properties );
         mStateMap = new TreeMap();
     }

     /**
      * Builds up the remote transfers state for all the sites. This reflects what is
      * set in the properties file.
      */
     public void buildState(){
         String site;
         Set sites;
         //build for stagein transfers
         buildState(TransferState.STAGE_IN_REMOTE_TYPE);

         //build for inter site transfers
         buildState(TransferState.INTER_REMOTE_TYPE);

         //build for stage out transfers
         buildState(TransferState.STAGE_OUT_REMOTE_TYPE);

         //build for all transfers
         buildState(TransferState.ALL_REMOTE_TYPE);

         //put the all sites (site = *) entry
         TransferState allState;
         if(containsKey(ALL_SITES)){
             allState = get(ALL_SITES);
         }
         else{
             allState = new TransferState();
             put(ALL_SITES,allState);
         }
        if(allState.getState() != 0x0){
            //apply the state to all sites
            for(Iterator it = mStateMap.values().iterator();it.hasNext();){
                TransferState state = (TransferState)it.next();
                state.set(allState.getState());
            }
        }
     }

     /**
      * Adds to the existing state table, state information for a particular
      * type of transfers.
      *
      * @param type   the type of transfer.
      */
     private void buildState(int type){
         String property = (String)propertyTable().get(new Integer(type));
         Set sites = getThirdPartySites(
                                   (type > TransferState.ALL_REMOTE_TYPE)?
                                   mProps.getThirdPartySitesRemote(property):
                                   mProps.getThirdPartySites(property)
                                   );
         String site;
         for(Iterator it = sites.iterator();it.hasNext();){
             site = (String)it.next();
             TransferState state = containsKey(site)?
                              get(site):
                              new TransferState();
             state.set(type);
             put(site, state);
         }
     }


     /**
      * Returns a boolean indicating whether to execute stage-in transfers on
      * remote site or not.
      *
      * @return boolean
      */
     public boolean stageInOnRemoteSite(String site){
         return containsKey(site)?
                get(site).get(TransferState.STAGE_IN_REMOTE_TYPE):
                //return the value for all sites
                get(ALL_SITES).get(TransferState.STAGE_IN_REMOTE_TYPE);
     }

     /**
      * Returns a boolean indicating whether to execute inter site transfers on
      * remote site or not.
      *
      * @return boolean
      */
     public boolean interOnRemoteSite(String site){
         return containsKey(site)?
                get(site).get(TransferState.INTER_REMOTE_TYPE):
                //return the value for all sites
                get(ALL_SITES).get(TransferState.INTER_REMOTE_TYPE);
     }

     /**
      * Returns a boolean indicating whether to execute stage-out transfers on
      * remote site or not.
      *
      * @return boolean
      */
     public boolean stageOutOnRemoteSite(String site){
         return containsKey(site)?
                get(site).get(TransferState.STAGE_OUT_REMOTE_TYPE):
                //return the value for all sites
                get(ALL_SITES).get(TransferState.STAGE_OUT_REMOTE_TYPE);
     }

     
     /**
      * Prints out the third party state for the various sites.
      */
     public void print(){
         StringBuffer sb = new StringBuffer();
         TransferState allSitesState = null;
         Object key;
         sb.append("Site |  SI_REMOTE, IN_REMOTE , SO_REMOTE");
         for(Iterator it = mStateMap.entrySet().iterator();it.hasNext();){
             Map.Entry entry = (Map.Entry)it.next();
             key = entry.getKey();
             if(key.equals(ALL_SITES)){
                 //store value for printing in the end
                 allSitesState = (TransferState)entry.getValue();
             }
             else{
                 sb.append('\n').append(key).append(" | ").append(entry.getValue());
             }
         }
         if(allSitesState != null){
             sb.append('\n').append(ALL_SITES).append("  ").append(" | ").append(allSitesState);
         }
         System.out.println(sb.toString());
     }



     /**
      * Returns whether there is an entry for a particular site or not.
      *
      * @param site   the site handle for a site.
      *
      * @return boolean
      */
     private boolean containsKey(String site){
         return mStateMap.containsKey(site);
     }


     /**
      * Inserts an entry in to the State Map, that maintains state of various
      * sites.
      *
      * @param site   the site handle for a site.
      * @param state the thirdparty state for the site.
      */
     private void put(String site,TransferState state){
         mStateMap.put(site, state);
     }

     /**
      * Returns the TPT state for a particular site.
      *
      * @param site the site handle for the site.
      * @return state the third party state for the site if there is an entry,
      *         else null.
      */
     private TransferState get(String site){
         Object state = mStateMap.get(site);
         return (state == null)?null:(TransferState)state;
     }

     /**
     * Returns a set of third party sites. An empty set is returned if value is
     * null.
     *
     * @param value  the value in the properties file.
     *
     * @return Set containing the names of the pools.
     */
    private Set getThirdPartySites(String value) {
        HashSet set = new HashSet();
        String site;
        if (value == null) {
            return set;
        }

        for (StringTokenizer st = new StringTokenizer(value, ",");st.hasMoreTokens();){
            site = (String) st.nextToken();
            /*
            mLogger.log(site + " is a third party enabled site " +
                        "for " + desc + " transfers",
                        LogManager.DEBUG_MESSAGE_LEVEL);
            */
            set.add(site);
        }
        return set;
    }


    /**
     * An inner class that holds the state for a particular site,as to whether to
     * execute transfers remotely or not.
     *
     */
    private class TransferState{

        /**
         * The constant to denote that a stage-in transfer is to be exectuted remotely
         */
        public static final int STAGE_IN_REMOTE_TYPE= 0x1;  //000001

        /**
         * The constant to denote that an inter site transfer is to be exectuted remotely
         */
        public static final int INTER_REMOTE_TYPE = 0x2;    //000010

        /**
         * The constant to denote that a stage-out transfer is to be exectuted remotely
         */
        public static final int STAGE_OUT_REMOTE_TYPE = 0x4;//000100

        /**
         * The constant to denote that all transfers are to be exectuted remotely
         */
        public static final int ALL_REMOTE_TYPE = 0x7;      //000111

        
        /**
         * Stores the state as an integer.
         */
        private int mState;

        /**
         * The default constructor.
         */
        public TransferState(){
            mState = 0x0;
        }

        /**
         * Returns the state.
         *
         * @return the state as an int
         */
        public int getState(){
            return mState;
        }


        /**
         * Sets a type of transfer to be third party.
         *
         * @param type the type of transfer to be set TPT
         */
        public void set(int type){
            //no type checking for time being
            mState = mState | type;
        }

        /**
         * Returns a boolean indicating whether the attribute passed is set
         * in the transfer state or not.
         * The attribute types are as constants in this class.
         *
         * @param type the attribute type.
         */
        public boolean get(int type){
            return ((mState & type) == type);
        }

       /**
        * Returns a textual description of the state as
        * (stageinRemote,interRemote,stageoutRemote).
        *
        * @return the textual description.
        */
       public String toString(){
           StringBuffer sb = new StringBuffer(36);
           sb.append('(').
                         append(this.get(TransferState.STAGE_IN_REMOTE_TYPE)).append("  ").append(',').
                         append(this.get(TransferState.INTER_REMOTE_TYPE)).append("  ").append(',').
                         append(this.get(TransferState.STAGE_OUT_REMOTE_TYPE)).
              append(')');
           return sb.toString();
       }

    }
}
